#include <iostream>
#include <vector>
#include <string>
#include <unistd.h>
#include <cstdlib>
#include <cassert>
#include <ctime>
#include <sys/wait.h>
#include <sys/types.h>
using namespace std;
#define PROCESS_NUM 5
#define Make_Seed() srand((unsigned long)time(nullptr)^getpid()^0X55^rand()%1234)
typedef void(*func_t)();//函数指针类型
//子进程需要完成的任务
void downLodeTask()
{
    cout<<getpid()<<"下载任务"<<endl;
    sleep(1);
}
void ioTask()
{
    cout<<getpid()<<"IO任务"<<endl;
    sleep(1);
}
void flushTask()
{
    cout<<getpid()<<"刷新任务"<<endl;
    sleep(1);
}
//多进程代码
class sunEndPoint
{
public:
    sunEndPoint(pid_t subId,int writeFd)
    :_subId(subId)
    ,_writeFd(writeFd)
    {
        char namebuffer[1000];
        snprintf(namebuffer,sizeof(namebuffer),"process-%d[pid(%d)-fd(%d)]",num++,_subId,_writeFd);
        _name=namebuffer;
    }
public:
    string _name;
    pid_t _subId;//pid
    int _writeFd;//写fd
    static int num;
};
int sunEndPoint::num=0;
void loadTaskFunc(vector<func_t>* out)
{
    assert(out);
    out->push_back(downLodeTask);
    out->push_back(ioTask);
    out->push_back(flushTask);
}
int recvTask(int readFd)
{
    int code=0;
    ssize_t s=read(readFd,&code,sizeof(code));
    if(s==sizeof(code))//合法信息
    {
        return code;
    }
    else if(s<=0)
    {
        return -1;
    }
    else 
        return 0;
}
void createSubProcess(vector<sunEndPoint>* subs,vector<func_t>& funcMap)
{
    vector<int> deleteFd;//解决下一个子进程拷贝父进程读端的问题
    for(int i=0;i<PROCESS_NUM;++i)
    {
        int fds[2];
        int n=pipe(fds);
        assert(n==0);
        (void)n;
        pid_t id=fork();
        if(id==0)//子进程
        {
            //关闭上一个文件的写端文件描述符
            for(int i=0;i<deleteFd.size();++i)
            {
                close(deleteFd[i]);
            }
            //子进程，处理任务
            close(fds[1]);
            while(true)
            {
                //1、获取命令码，如果父进程没有发送，子进程被阻塞
                int commandCode=recvTask(fds[0]);
                //2、完成任务
                if(commandCode>=0&&commandCode<funcMap.size())
                {
                    funcMap[commandCode]();
                }
                else if(commandCode==-1)
                {
                    break;
                }
            }
           exit(0); 
        }
        close(fds[0]);
        sunEndPoint sub(id,fds[1]);
        subs->push_back(sub);
        
        deleteFd.push_back(fds[1]);
    }
}
void sendTask(const sunEndPoint& process,int taskNum)
{
    cout<<"send task num"<<taskNum<<"send to->"<<process._name<<endl;
    int n=write(process._writeFd,&taskNum,sizeof(taskNum));
    assert(n==sizeof(int));//判断是否成功写入4个字节
    (void)n;
}
void loadBlanceContrl(const vector<sunEndPoint>& subs,const vector<func_t>& funcMap,int count)
{
    int processnum =subs.size();//子进程的个数
    int tasknum=funcMap.size();
    bool forever=(count==0?true:false);
    while(true)
    {
        //选择一个子进程,从vector<sunEndPoint>选择一个index
        int subIdx=rand()%processnum;
        //选择一个任务,从vector<func_t>选择一个index
        int taskIdx=rand()%tasknum;
        //将任务发送给指定的子进程,将一个任务的下标发送给子进程
        sendTask(subs[subIdx],taskIdx);//taskIdx作为管道的大小4个字节
        sleep(1);
        if(!forever)//forever不为0
        {
            --count;
            if(count==0)
                break;
        }
    }
    //写端退出，读端将管道内数据读完后read返回0
    for(int i=0;i<processnum;++i)
    {
        close(subs[i]._writeFd);//后续被创建的子进程拥有首个创建的子进程的父进程的读端，所以这里其实是后创建的进程先关闭
    }
}
//回收子进程
void waitProcess(vector<sunEndPoint> processes)
{
    int processnum=processes.size();
    for(int i=0;i<processnum;++i)
    {
        waitpid(processes[i]._subId,nullptr,0);
        cout<<"wait sub process success"<<processes[i]._subId<<endl;
    }
}
//父进程给子进程发布命令，父进程写，子进程读
int main()
{
    Make_Seed();//创建随机数
    //父进程创建子进程及和子进程通信的管道
    vector<func_t> funcMap;//vector<函数指针> funcMap
    loadTaskFunc(&funcMap);//加载任务
    vector<sunEndPoint> subs;//子进程集合
    createSubProcess(&subs,funcMap);//维护父子通信信道
    //这里的程序是父进程，用于控制子进程
    int taskCnt=9;//让子进程做9个任务
    loadBlanceContrl(subs,funcMap,taskCnt);
    //回收子进程信息
    waitProcess(subs);
    return 0;
}